package no.ffi.gismoidm.certstore;

import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXParameters;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import javax.security.auth.x500.X500Principal;
import no.ffi.gismoidm.idp.Config;
import no.ffi.gismoidm.utils.CertificateNotValidatedException;

/**
 * Implements a certificate service based on file storage with names from hash calculations. 
 * The certificates are stored in PEM format with a filename equal to its subject hash
 * 
 * @author anders
 */
public class HashnameStorageService implements CertificateService {
    
    KeyStore anchors;
    static String certDirectory;
    
    HashnameStorageService(X509Certificate trustAnchor) throws Exception {
        certDirectory = Config.getProperty("CA_CERTDIR");
        if (!certDirectory.endsWith("/")) certDirectory += "/";

        
        // Establish the trust anchor (given in a PEM file)
        anchors = KeyStore.getInstance(KeyStore.getDefaultType());
        anchors.load(null);
        if (trustAnchor != null) // TODO remove later
            anchors.setCertificateEntry("CA",trustAnchor);
        
        // Check if there is a trust root for TPM devices
        try {
            X509Certificate tpmroot = CertificateFileOperations.loadPemCert(certDirectory + "TPMrootcert.pem");
            anchors.setCertificateEntry("TPM",tpmroot);
        } catch (IOException e1) { } // No tpm root cert available
          catch (Exception e2) { e2.printStackTrace(); }
        
    }
    
    private X509Certificate findDNcert(X500Principal xp) throws Exception {
        String stem = CertificateFileOperations.getX500PrincipalHash(xp);
        X509Certificate cert;
        char suffix = '0'; String filename="";
        while (true) {
            filename = certDirectory + stem + '.' + suffix;
            File f = new File(filename);
            if (!f.exists()) return null;
            try { // try first to load PEM format, then DER
                cert = CertificateFileOperations.loadPemCert(filename);
            } catch (java.security.cert.CertificateException ce) {
                cert = CertificateFileOperations.loadDERCert(filename);
            }
            // If this is the correct cert then return it
            if (xp.equals(cert.getSubjectX500Principal())) return cert;
            // else try a higher suffix number
            suffix++;
        }
    }
    
    private static X509Certificate findHashCert(String issuerHash, String serialNumber) throws Exception {
        X509Certificate cert;
        char suffix = '0'; String filename="";
        while (true) {
            filename = certDirectory + issuerHash + '.' + suffix;
            File f = new File(filename);
            if (!f.exists()) return null;
            try { // try first to load PEM format, then DER
                cert = CertificateFileOperations.loadPemCert(filename);
            } catch (java.security.cert.CertificateException ce) {
                cert = CertificateFileOperations.loadDERCert(filename);
            }
            // If this is the correct cert then return it
//          if (serialNumber == null) return cert; // TODO May not be a good idea
            String aikSerial = cert.getSerialNumber().toString();
            if (aikSerial.equals(serialNumber)) return cert;
            // else try a higher suffix number
            suffix++;
        }
        
    }
    
    private static String findDNfilename(X500Principal xp) throws Exception {
        String stem = CertificateFileOperations.getX500PrincipalHash(xp);
        X509Certificate cert;
        char suffix = '0'; String filename="";
        while (true) {
            filename = certDirectory + stem + '.' + suffix;
            File f = new File(filename);
            if (!f.exists()) return null;
            try { // try first to load PEM format, then DER
                cert = CertificateFileOperations.loadPemCert(filename);
            } catch (java.security.cert.CertificateException ce) {
                cert = CertificateFileOperations.loadDERCert(filename);
            }
            // If this is the correct cert then return it
            if (xp.equals(cert.getSubjectX500Principal())) return filename;
            // else try a higher suffix number
            suffix++;
        }
    }
    
    

    @Override
    public X509Certificate retrieveAndValidate(X500Principal dn) throws Exception {
        // First, build a certificate path up to the first self-signed certificate
        System.out.println("Retrieve and validate for " + dn.getName());
        ArrayList certPath = new ArrayList();
        X500Principal dn1, dn0 = dn;
        X509Certificate c0=null;
        while (true) { // Follow signature chain
            try {
                 c0 = findDNcert(dn0);
            } catch (Exception e) 
                {throw new CertificateNotValidatedException("Certificate file read error:" + e.getMessage());}
            if (c0==null) break;
            dn1 = c0.getIssuerX500Principal();
            if (dn1.equals(dn0)) break; // Self-signed
            certPath.add(c0);
            dn0 = dn1;
        }
        if (certPath.size()>0 && validate(certPath)) 
            return (X509Certificate)certPath.get(0);
        else throw new CertificateNotValidatedException("Certificate to " + dn + " could not be validated");
    }
    
    @Override
    public X509Certificate retrieveAndValidate(String subjectHash, String serialNumber) throws Exception {
        // First, build a certificate path up to the first self-signed certificate
        System.out.println("Retrieve and validate for " + subjectHash + ":" + serialNumber);
        ArrayList certPath = new ArrayList();
        X509Certificate c0 = null;
        try {
            c0 = findHashCert(subjectHash,serialNumber);
        } catch (Exception e) 
                {throw new CertificateNotValidatedException("Certificate file read error:" + e.getMessage());}
        certPath.add(c0);
        
        X500Principal dn1, dn0 = c0.getIssuerX500Principal();

        while (true) { // Follow signature chain
            try {
                 c0 = findDNcert(dn0);
            } catch (Exception e) 
                {throw new CertificateNotValidatedException("Certificate file read error:" + e.getMessage());}
            if (c0==null) break;
            dn1 = c0.getIssuerX500Principal();
            if (dn1.equals(dn0)) break; // Self-signed
            certPath.add(c0);
            dn0 = dn1;
        }
        if (certPath.size()>0 && validate(certPath)) 
            return (X509Certificate)certPath.get(0);
        else throw new CertificateNotValidatedException("Certificate could not be validated");
    }

        
    // Now create a certpath validator to validate the certpath
    private boolean validate(List<X509Certificate> certPath) {
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            PKIXParameters pp = new PKIXParameters(anchors);
            pp.setRevocationEnabled(false);
            pp.setPolicyQualifiersRejected(false);
            CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
            CertPath cp = cf.generateCertPath(certPath);
            cpv.validate(cp, pp);
        } catch (GeneralSecurityException gse) {
            System.out.println("CertPathValidation exception: " + gse);
            return false;
        }
        return true;
    }     
    
}
